;; EX-22.scm - FM synth mass production

;; This file is a translation of a set of examples James McCartney
;; posted to sc-users on Fri Nov 7 20:49:24 PST 2003.  It defines 100
;; randomly generated FM instruments and plays them, randomly.  There
;; are two versions of the instruments, percussive and sustaining.
;; The maximum attack time for a note can be set.

(define-procedure (mfm (sustained-variant #f) 
		       (max-attack 0.4))

  ;; Various random generator names used by SC3.  Note that the SC3
  ;; generators do not have integer variants, the generators give
  ;; integer results if the inputs are integers.  This is not
  ;; straightforward to implement in scheme since (integer? 1.0) is
  ;; '#t', but on the other hand 1.0 is an integer...

  (define (exprand a b) (random* random-exponential-normal a b))
  (define (rrand a b) (random a b))
  (define (linrand a) (random a))
  (define (rand a) (random a))
  (define (rand2 a) (random (- a) a))
  (define (coin a) (< (random) a))
  (define (choose a) (random a))
  (define (randI a) (random-integer a))
  (define (rrandI a b) (random-integer a b))

  (define-procedure (make-carrier (freq 440) (mod 0) (mix 0) (gate 1))
    (let ((e (if sustained-variant
		 (Env.adsr (exprand 0.001 max-attack) 
			   (linrand 3)
			   (square (rrand 0.4 1))
			   (rrand 0.001 0.2))
		 (Env.perc (exprand 0.001 max-attack) (exprand 0.1 2.0))))
	  (m (+ (linrand 10) 1)))
      (SinOsc.ar (Mul freq m) 
		 mod 
		 (EnvGen.kr envelope: e 
			    gate: gate 
			    levelScale: (square (rand (rrand 0.5 0.6))))
		 mix)))

  (define-procedure (make-middle (freq 440) (mod 0) (mix 0) (gate 1))
    (let ((e (if sustained-variant
		 (Env.adsr (exprand 0.001 max-attack)
			   (linrand 3) 
			   (square (rand 1)) 
			   (rrand 0.001 0.2))
		 (Env.perc (exprand 0.001 max-attack) (exprand 0.1 2.0))))
	  (m (+ (linrand 5) 1)))
      (SinOsc.ar (Mul freq m) 
		 mod 
		 (EnvGen.kr envelope: e 
			    gate: gate 
			    levelScale: (square (rand 3)))
		 mix)))

  (define-procedure (make-modulator (freq 440) (mix 0) (gate 1))
    (let ((e (if sustained-variant
		 (Env.adsr (exprand 0.001 max-attack)
			   (linrand 3)
			   (square (rand 1)) 
			   (rrand 0.001 0.2))
		 (Env.perc (exprand 0.001 max-attack) (exprand 0.1 2.0))))
	  (m (+ (linrand 5) 1)))
      (SinOsc.ar (Mul freq m) 
		 (cube (rand 1.3))
		 (EnvGen.kr envelope: e 
			    gate: gate 
			    levelScale: (square (rand 3)))
		 mix)))

  (define (make-configuration freq gate)
    (define (build-A f c gate)
      (make-carrier f (make-modulator f 0 gate) c gate))
    (define (build-B f c gate) 
      (make-carrier f (make-middle f (make-modulator f 0 gate) 0 gate) c gate))
    (define (build-C f c gate)
      (make-carrier f (make-carrier f (make-modulator f 0 gate) c gate) c gate))
    (let* ((z (randI 3))
	   (builder (ref (list build-A build-B build-C) z))
	   (limit (ref (list 3 2 2) z)))
      (let loop ((c 0)
		 (n 0))
	(if (= n limit)
	    c
	    (let ((f (Add freq (square (rand2 1.8)))))
	      (loop (builder f c gate) (+ n 1)))))))

  ;; This form atually makes the instruments, it will take some time...

  (define (make-instruments)
    (inform "Making instruments takes time...")
    (inform 
     "Elapsed time: "
     (car
      (time-procedure 
       utc
       (lambda ()
	 (synthdef-send
	  (synthdef 
	   "reverb" ()
	   (let loop ((in (In.ar 0 2))
		      (n 0))
	     (if (= n 5)
		 (ReplaceOut.ar 0 in)
		 (loop (AllpassN.ar in 0.05 
				    (list (rand 0.05) (rand 0.05))) (+ n 1))))))
	 (iterate 
	  100
	  (lambda (i)
	    (synthdef-send
	     (synthdef 
	      (format "fmgen_a_~a" i)
	      ((freq 440) (amp 1) (gate 1) (pan 0))
	      (let ((c (make-configuration freq gate)))
		(DetectSilence.ar c doneAction: 2)
		(Out.ar 0 (Pan2.ar c pan amp))))))))))))
  
  (define (play)
    (-> "/s_new" "reverb" 1000 1 1)
    (@r 
     (yield)
     (let loop ((t (utc))
		(name (format "fmgen_a_~a" (randI 100)))
		(timescale (exprand 0.67 1.5)))
       (let ((elapse (* timescale (choose '(0.1 0.1 0.1 0.2 0.2 0.4 0.8 1.6)))))
	 (iterate 
	  (choose '(1 1 1 1 1 2 2 3))
	  (lambda ()
	    (let ((freq (midi-note->frequency (rrandI 24 86)))
		  (id (nID)))
	      (=> t (list "s_new" name id 0 1 "freq" freq "amp" 0.4 "pan" (rand2 1)))
	      (if sustained-variant 
		  (=> (+ t elapse) (list "/n_set" id "gate" 0))))))
	 (loop (yield elapse)
	       (if (coin 0.16) (format "fmgen_a_~a" (randI 100)) name)
	       (if (coin 0.25) (exprand 0.67 1.5) timescale))))))
  
  (lambda (command)
    (case command
      ((make) (make-instruments))
      ((play) (play))
      ((stop) (begin
		(tschedule-clear! (current-tschedule))
		(-> "n_free" 1000))))))

;; Make generator.

(define m (mfm sustained-variant: #t))

;; Make instruments.

(m 'make)

;; Play.

(m 'play)

;; Stop listening.

(m 'stop)
